package ace.module.account.core.impl.manager.impl;

import ace.cmp.core.constants.CoreConstant;
import ace.cmp.core.exception.BusinessException;
import ace.cmp.spring.data.jdbc.querydsl.core.converter.PageConverter;
import ace.cmp.util.AceStreamUtils;
import ace.module.account.api.enums.IamAccountPassportPassportTypeEnum;
import ace.module.account.api.model.dto.IamAccountPassportDto;
import ace.module.account.api.model.input.passport.IamAccountPassportFilterInput;
import ace.module.account.core.impl.dao.entity.IamAccount;
import ace.module.account.core.impl.dao.entity.IamAccountPassport;
import ace.module.account.core.impl.dao.entity.QIamAccountPassport;
import ace.module.account.core.impl.dao.entity.QIamAccountTag;
import ace.module.account.core.impl.dao.repository.IamAccountPassportRepository;
import ace.module.account.core.impl.manager.IamAccountPassportManager;
import ace.module.common.core.impl.manager.impl.AbstractEntityOfAppManager;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Predicate;
import com.querydsl.sql.AbstractSQLQuery;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author caspar
 * @date 2023/3/20 9:28
 */
@Transactional(readOnly = true)
@Slf4j
@AllArgsConstructor
@Component
public class IamAccountPassportManagerImpl
    extends AbstractEntityOfAppManager<IamAccountPassport, Long, IamAccountPassportDto>
    implements IamAccountPassportManager {

  private final IamAccountPassportRepository iamAccountPassportRepository;
  private final PageConverter pageConverter;

  @Override
  public IamAccountPassport getByIamAppIdAndTagAndPassportAndPassportType(
      Long iamAppId, String tag, String passport, Integer passportType) {
    return this.getByIamAppIdAndTagAndPassportAndPassport2AndPassport3AndPassportType0(
            iamAppId, tag, passport, "", "", List.of(passportType))
        .stream()
        .findFirst()
        .orElse(null);
  }

  @Override
  public List<IamAccountPassport> getByIamAppIdAndTagAndPassportAndPassportTypes(
      Long iamAppId, String tag, String passport, List<Integer> passportTypes) {
    return this.getByIamAppIdAndTagAndPassportAndPassport2AndPassport3AndPassportType0(
        iamAppId, tag, passport, "", "", passportTypes);
  }

  @Override
  public List<Long> getAccountIdByIamAppIdAndTagAndPassportAndPassportTypes(
      Long iamAppId, String tag, String passport, List<Integer> passportTypes) {
    return this.getByIamAppIdAndTagAndPassportAndPassport2AndPassport3AndPassportType0(
            iamAppId, tag, passport, "", "", passportTypes)
        .stream()
        .map(IamAccountPassport::getIamAccountId)
        .collect(Collectors.toList());
  }

  @Override
  public IamAccountPassport getByIamAppIdAndTagAndPassportAndPassport2AndPassport3AndPassportType(
      Long iamAppId,
      String tag,
      String passport,
      String passport2,
      String passport3,
      Integer passportType) {
    return this.getByIamAppIdAndTagAndPassportAndPassport2AndPassport3AndPassportType0(
            iamAppId, tag, passport, passport2, passport3, List.of(passportType))
        .stream()
        .findFirst()
        .orElse(null);
  }

  @Override
  public List<IamAccountPassport> getByIamAppIdAndTagAndPassportAndPassportTypes(
      Long iamAppId, String passport, List<Integer> passportTypes) {
    return this.getByIamAppIdAndTagAndPassportAndPassport2AndPassport3AndPassportType0(
        iamAppId, null, passport, "", "", passportTypes);
  }

  @Override
  public Map<Long, String> getPhonePassportMapByAccountId(List<Long> iamAccountIdList) {

    return this.getPhonePassportEntityMapByIamAccountIdIn(iamAccountIdList)
        .entrySet()
        .stream()
        .collect(HashMap::new,
            (m, entry) -> {
              m.put(entry.getKey(), entry.getValue().getPassport());
            }, HashMap::putAll);
  }

  @Override
  public List<IamAccountPassport> getPhonePassportByIamAccountIdIn(List<Long> iamAccountIdList) {
    if (CollectionUtils.isEmpty(iamAccountIdList)) {
      return new ArrayList<>();
    }
    QIamAccountPassport qIamAccountPassport = QIamAccountPassport.iamAccountPassport;
    return this.iamAccountPassportRepository.queryMany(sqlQuery ->
        sqlQuery
            .select(qIamAccountPassport)
            .from(qIamAccountPassport)
            .where(
                ExpressionUtils.allOf(
                    qIamAccountPassport.iamAccountId.in(iamAccountIdList),
                    qIamAccountPassport.passportType.eq(IamAccountPassportPassportTypeEnum.PHONE.getCode()),
                    qIamAccountPassport.deleteFlag.eq(CoreConstant.DELETE_FLAG_NOT)
                )
            )
    );
  }

  @Override
  public List<IamAccountPassport> getPhonePassportByIamAccountIdIn(Long iamAppId, List<Long> iamAccountIdList) {
    if (CollectionUtils.isEmpty(iamAccountIdList) || Objects.isNull(iamAppId)) {
      return new ArrayList<>();
    }
    QIamAccountPassport qIamAccountPassport = QIamAccountPassport.iamAccountPassport;
    return this.iamAccountPassportRepository.queryMany(sqlQuery ->
        sqlQuery
            .select(qIamAccountPassport)
            .from(qIamAccountPassport)
            .where(
                ExpressionUtils.allOf(
                    qIamAccountPassport.iamAppId.eq(iamAppId),
                    qIamAccountPassport.iamAccountId.in(iamAccountIdList),
                    qIamAccountPassport.passportType.eq(IamAccountPassportPassportTypeEnum.PHONE.getCode()),
                    qIamAccountPassport.deleteFlag.eq(CoreConstant.DELETE_FLAG_NOT)
                )
            )
    );
  }

  @Override
  public IamAccountPassport getPhonePassportByIamAccountId(Long iamAppId, Long iamAccountId) {
    return this.iamAccountPassportRepository.getByIamAppIdAndIamAccountIdAndPassportTypeAndDeleteFlag(
        iamAppId,
        iamAccountId,
        IamAccountPassportPassportTypeEnum.PHONE.getCode(),
        CoreConstant.DELETE_FLAG_NOT
    );
  }

  @Override
  public Map<Long, IamAccountPassportDto> getAccountIdAndUsernamePassportDtoMapByAccountId(List<Long> iamAccountIds) {
    return this.getIamAccountIdAndPassportDtoMapByIamAccountIdAndPassportType(iamAccountIds, IamAccountPassportPassportTypeEnum.USERNAME);
  }

  @Override
  public Map<Long, IamAccountPassportDto> getAccountIdAndPhonePassportDtoMapByAccountId(List<Long> iamAccountIds) {
    return this.getIamAccountIdAndPassportDtoMapByIamAccountIdAndPassportType(iamAccountIds, IamAccountPassportPassportTypeEnum.PHONE);
  }

  @Override
  public Map<Long, IamAccountPassportDto> getIamAccountIdAndPassportDtoMapByIamAccountIdAndPassportType(
      List<Long> iamAccountIds,
      IamAccountPassportPassportTypeEnum passportPassportTypeEnum
  ) {

    Map<Long, IamAccountPassportDto> result = this.getIamAccountIdAndPassportMapByIamAccountIdAndPassportType(iamAccountIds, passportPassportTypeEnum)
        .values()
        .stream()
        .map(this::convertToEntityDto)
        .collect(
            HashMap::new,
            (m, entry) -> {
              m.put(entry.getIamAccountId(), entry);
            },
            HashMap::putAll
        );

    return result;
  }

  @Override
  public Map<Long, IamAccountPassport> getIamAccountIdAndPassportMapByIamAccountIdAndPassportType(
      List<Long> iamAccountIds,
      IamAccountPassportPassportTypeEnum passportPassportTypeEnum
  ) {
    List<IamAccountPassport> iamAccountPassportList = this.iamAccountPassportRepository.getByIamAccountIdInAndPassportTypeAndDeleteFlag(
        iamAccountIds,
        passportPassportTypeEnum.getCode(),
        CoreConstant.DELETE_FLAG_NOT
    );

    Map<Long, IamAccountPassport> result = iamAccountPassportList.stream()
        .collect(
            HashMap::new,
            (m, entry) -> {
              m.put(entry.getIamAccountId(), entry);
            },
            HashMap::putAll
        );

    return result;
  }

  @Override
  public IamAccountPassport getUsernamePassportByIamAccountId(Long iamAccountId) {

    return this.iamAccountPassportRepository.getByIamAccountIdAndPassportTypeAndDeleteFlag(
        iamAccountId,
        IamAccountPassportPassportTypeEnum.USERNAME.getCode(),
        CoreConstant.DELETE_FLAG_NOT
    );
  }


  @Override
  public Page<IamAccountPassportDto> search(Long iamAppId, IamAccountPassportFilterInput filter,
      Pageable pageable) {
    QIamAccountPassport qIamAccountPassport = new QIamAccountPassport("passport");
    List<Predicate> predicates = new ArrayList<>();
    predicates.add(qIamAccountPassport.iamAppId.eq(iamAppId));
    predicates.add(qIamAccountPassport.deleteFlag.eq(CoreConstant.DELETE_FLAG_NOT));

    predicates.add(Optional.ofNullable(filter)
        .map(IamAccountPassportFilterInput::getAccountId)
        .map(p -> qIamAccountPassport.iamAccountId.eq(p))
        .orElse(null)
    );

    predicates.add(Optional.ofNullable(filter)
        .map(IamAccountPassportFilterInput::getPassportType)
        .map(p -> qIamAccountPassport.passportType.eq(p))
        .orElse(null)
    );

    predicates.add(Optional.ofNullable(filter)
        .map(IamAccountPassportFilterInput::getPassport)
        .map(p -> qIamAccountPassport.passport.eq(p))
        .orElse(null)
    );

    predicates.add(Optional.ofNullable(filter)
        .map(IamAccountPassportFilterInput::getPassport2)
        .map(p -> qIamAccountPassport.passport2.eq(p))
        .orElse(null)
    );

    predicates.add(Optional.ofNullable(filter)
        .map(IamAccountPassportFilterInput::getPassport3)
        .map(p -> qIamAccountPassport.passport3.eq(p))
        .orElse(null)
    );

    Page<IamAccountPassport> page = this.iamAccountPassportRepository.page(predicates, pageable);

    return page.map(p -> this.getEntityToDtoConverter().convert(p));
  }

  @Override
  public Map<Long, IamAccountPassport> getPhonePassportEntityMapByIamAccountIdIn(List<Long> iamAccountIdList) {
    return AceStreamUtils.ofNullable(this.getPhonePassportByIamAccountIdIn(iamAccountIdList))
        .collect(HashMap::new,
            (m, entry) -> {
              m.put(entry.getIamAccountId(), entry);
            },
            HashMap::putAll);
  }

  @Override
  public Map<Long, IamAccountPassport> getPhonePassportEntityMapByIamAccountIdIn(
      Long iamAppId,
      List<Long> iamAccountIdList) {
    return AceStreamUtils.ofNullable(this.getPhonePassportByIamAccountIdIn(iamAppId, iamAccountIdList))
        .collect(HashMap::new,
            (m, entry) -> {
              m.put(entry.getIamAccountId(), entry);
            },
            HashMap::putAll);
  }

  @Override
  public void checkExistsByUserName(Long iamAppId, String username) {
    if (this.iamAccountPassportRepository.existsByIamAppIdAndPassportTypeAndPassportAndDeleteFlag(
        iamAppId,
        IamAccountPassportPassportTypeEnum.USERNAME.getCode(),
        username,
        CoreConstant.DELETE_FLAG_NOT)) {
      throw new BusinessException("账户名已经存在");
    }
  }

  @Override
  public IamAccountPassport buildUsernamePassport(Long iamAccountId, Long iamAppId, String username) {
    return IamAccountPassport.builder()
        .id(this.getNewId())
        .iamAccountId(iamAccountId)
        .iamAppId(iamAppId)
        .passportType(IamAccountPassportPassportTypeEnum.USERNAME.getCode())
        .passport(username)
        .passport2(StringUtils.EMPTY)
        .passport3(StringUtils.EMPTY)

        .deleteFlag(CoreConstant.DELETE_FLAG_NOT)
        .createTime(Instant.now().toEpochMilli())
        .updateTime(Instant.now().toEpochMilli())
        .rowVersion(CoreConstant.DEFAULT_ROW_VERSION)
        .build();
  }

  @Override
  public IamAccountPassport buildPhonePassport(Long iamAccountId, Long iamAppId, String phone) {
    return IamAccountPassport.builder()
        .id(this.getNewId())
        .iamAccountId(iamAccountId)
        .iamAppId(iamAppId)
        .passportType(IamAccountPassportPassportTypeEnum.PHONE.getCode())
        .passport(phone)
        .passport2(StringUtils.EMPTY)
        .passport3(StringUtils.EMPTY)

        .createTime(Instant.now().toEpochMilli())
        .updateTime(Instant.now().toEpochMilli())
        .rowVersion(CoreConstant.DEFAULT_ROW_VERSION)
        .build();
  }

  @Override
  public IamAccountPassport buildWxMiniAppPassport(Long iamAccountId, String wxMiniAppId, String openId, String unionId) {
    return IamAccountPassport.builder()
        .id(this.getNewId())
        .iamAccountId(iamAccountId)
        .iamAppId(iamAccountId)
        .passportType(IamAccountPassportPassportTypeEnum.PHONE.getCode())
        .passport(wxMiniAppId)
        .passport2(openId)
        .passport3(unionId)

        .createTime(Instant.now().toEpochMilli())
        .updateTime(Instant.now().toEpochMilli())
        .rowVersion(CoreConstant.DEFAULT_ROW_VERSION)
        .build();
  }

  @Transactional(rollbackFor = Throwable.class)
  @Override
  public void addOrModifyByUsername(IamAccount iamAccount, String username) {
    if (StringUtils.isBlank(username)) {
      return;
    }
    IamAccountPassport oldIamAccountPassport = this.getUsernamePassportByIamAccountId(iamAccount.getId());

    if (oldIamAccountPassport != null
        && oldIamAccountPassport.getPassport().equalsIgnoreCase(username)) {
      return;
    }

    this.checkExistsByUserName(iamAccount.getIamAppId(), username);

    IamAccountPassport iamAccountPassport = oldIamAccountPassport;
    if (iamAccountPassport == null) {
      iamAccountPassport = this.buildUsernamePassport(iamAccount.getId(), iamAccount.getIamAppId(), username);
    }
    iamAccountPassport.setPassport(username);

    this.save(iamAccountPassport, false);
  }

  @Override
  public void checkExistsWxMiniAppIdAndOpenId(Long iamAppId, String wxMiniAppId, String openId) {
    if (this.iamAccountPassportRepository
        .existsByIamAppIdAndPassportAndPassport2AndPassportTypeAndDeleteFlag(
            iamAppId,
            wxMiniAppId,
            openId,
            IamAccountPassportPassportTypeEnum.WX_MINI_APP_OPENID.getCode(),
            CoreConstant.DELETE_FLAG_NOT)) {
      throw new BusinessException("该微信已经注册");
    }
  }

  @Override
  public void checkExistsPhoneNumber(Long iamAppId, String phoneNumber) {
    if (this.iamAccountPassportRepository.existsByIamAppIdAndPassportTypeAndPassportAndDeleteFlag(
        iamAppId,
        IamAccountPassportPassportTypeEnum.PHONE.getCode(),
        phoneNumber,
        CoreConstant.DELETE_FLAG_NOT)) {
      throw new BusinessException("手机号码已经存在");
    }
  }


  private List<IamAccountPassport> getByIamAppIdAndTagAndPassportAndPassport2AndPassport3AndPassportType0(
      Long iamAppId,
      String tag,
      String passport,
      String passport2,
      String passport3,
      List<Integer> passportTypes) {
    return iamAccountPassportRepository.query(
        sqlQuery -> {
          QIamAccountPassport qIamAccountPassport = QIamAccountPassport.iamAccountPassport;
          QIamAccountTag qIamAccountTag = QIamAccountTag.iamAccountTag;

          AbstractSQLQuery queryBase =
              sqlQuery
                  .distinct()
                  .select(qIamAccountPassport)
                  .from(qIamAccountPassport)
                  .leftJoin(qIamAccountTag)
                  .on(qIamAccountPassport.iamAccountId.eq(qIamAccountTag.iamAccountId))
                  .where(
                      ExpressionUtils.allOf(
                          qIamAccountPassport.iamAppId.eq(iamAppId),
                          qIamAccountPassport.passport.eq(passport),
                          StringUtils.isNotBlank(passport2)
                              ? qIamAccountPassport.passport2.eq(passport2)
                              : qIamAccountPassport.passport2.eq(""),
                          StringUtils.isNotBlank(passport3)
                              ? qIamAccountPassport.passport3.eq(passport3)
                              : qIamAccountPassport.passport3.eq(""),
                          qIamAccountPassport.passportType.in(passportTypes),
                          qIamAccountPassport.deleteFlag.eq(CoreConstant.DELETE_FLAG_NOT),
                          StringUtils.isNotBlank(tag) ? qIamAccountTag.key.eq(tag) : null,
                          StringUtils.isNotBlank(tag)
                              ? qIamAccountTag.deleteFlag.eq(CoreConstant.DELETE_FLAG_NOT)
                              : null));
          if (log.isDebugEnabled()) {
            log.debug(
                "getByIamAppIdAndTagAndPassportAndPassport2AndPassport3AndPassportType0,{}",
                queryBase.toString());
          }
          return queryBase.fetch();
        });
  }
}
